#include <stdio.h>
#include <string.h>
#include <cstring>
#include <stdlib.h>
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

const char* UserName() //获取用户名
{
    const char* user_name = getenv("USER");
    return (user_name == nullptr ? "None" : user_name);
}

const char* HostName() //获取主机名
{
    const char* host_name = getenv("HOSTNAME");
    return host_name == NULL ? "None" : host_name;
}

char cwd[1024]; //先定义一个全局的cwd
char cwdenv[1024]; //全局的cwd环境变量
const char* GetPwd() //获取当前路径
{
    //const char* pwd = getenv("PWD"); //环境变量获取
    const char* pwd = getcwd(cwd, sizeof(cwd)); //系统调用获取
    if(pwd != NULL)
    {
        snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd); //格式化
        putenv(cwdenv);
    }
    return pwd == NULL ? "None" : pwd;
}

char *g_env[100]; //指向环境变量的指针数组
int g_env_num = 0; //环境变量的个数
void EnvInit()
{
    //1.先获取环境变量
    extern char ** environ; //外部全局变量
    //memset(g_env, 0, sizeof(g_env)); 
    for(int i = 0; environ[i]; i++)
    {
        g_env[i] = (char*)malloc(strlen(environ[i]) + 1); //申请空间
        if(g_env[i] == NULL) 
        {
            perror("malloc fail!\n");
            return;
        }
        strcpy(g_env[i], environ[i]); //拷贝父进程的环境变量
        g_env_num++;
    }
    g_env[g_env_num] = NULL; //环境变量表以NULL结尾

    //2.把得到的环境变量导到自己的进程地址空间里
    for(int i = 0; g_env[i]; i++)
    {
        putenv(g_env[i]);
    }
}



#define FORMAT "%s@%s:%s# " //设置命令行提示符的格式
void PrintCmdPrompt()
{
    printf(FORMAT,UserName(), HostName(),GetPwd());
    //printf("%s@lyj-alicloud:%s# ",UserName(), GetPwd());

}

#define COMMAND_SIZE 1024
bool GetArguments(char* commandline, int size) //获取命令行参数
{
    char* arg = fgets(commandline, size, stdin);
    if(arg == NULL) //获取失败 
        return false;
    commandline[strlen(commandline)-1] = 0; //清理多余的\n
    if(strlen(commandline) == 0) //如果是直接回车的情况
        return false;
    return true;
}

#define DELIM " " //分隔符要用双引号
char *g_argv[128];
int g_argc = 0;
bool CommandParse(char *commandline)
{
    g_argc = 0;//每次都要置0，否则argc会被累加
    g_argv[g_argc++] = strtok(commandline, DELIM);
    while( g_argv[g_argc++] = strtok(nullptr, DELIM) );
    g_argc--; //后置++，导致argc多加了一次，这里要-1
    return g_argc > 0 ? true : false; //返回时判断一下
}

void PrintArg()
{
    for(int i = 0; g_argv[i]; i++)
    {
        printf("argv[%d]:%s\n", i, g_argv[i]);
    }
    printf("argc:%d\n", g_argc);
}


const char* GetHome()
{
    const char* home = getenv("HOME");
    return home == NULL ? "None" : home;
}
void Cd()
{
    if(g_argc == 1) //cd后什么都不加，回到家目录
    {
        std::string home = GetHome();
        if(home == "None") //没获取到家目录直接返回
            return; 
        chdir(home.c_str());
    }
    else //cd后加路径
    {
        std::string targ_dir = g_argv[1];
        chdir(targ_dir.c_str()); //直接去目标路径
    }
    return;
}

int last_exit_code = 0;
void Echo()
{
    //只输入了echo,不做处理直接返回 
    if(g_argc == 1) return;
    
    std::string opt = g_argv[1];
    if(opt == "$?") //打印最近一个程序的退出码
    {
        std::cout << last_exit_code << std::endl;
        last_exit_code = 0; //置0
    }
    else if(opt[0] == '$') //如 echo $PATH ，打印环境变量PATH
    {
        std::string env_name = opt.substr(1); //env_name里存的就是$后的内容
        const char* env_value =  getenv(env_name.c_str());
        if(env_value) 
            std::cout << env_value << std::endl; //打印环境变量的值
    }
    else 
    {
        std::cout << opt << std::endl;
    }
    return;
}

bool CheckAndExeBuiltinCmd() //检查并执行内建命令
{
    std::string cmd = g_argv[0];
    if(cmd == "cd")
    {
        Cd();
        return true;
    }
    else if(cmd == "echo")
    {
        Echo();
        return true;
    }
    return false;
}


int Execute()
{
    pid_t id = fork();
    if(id == 0) //子进程：程序替换
    {
        execvp(g_argv[0], g_argv);
    }
    //父进程：阻塞等待
    int status;
    pid_t rid = waitpid(id, &status, 0);
    if(rid > 0)
    {
        if(WIFEXITED(status)) //正常退出
        {
            last_exit_code = WEXITSTATUS(status); //获取退出码
        }
        else
            last_exit_code = 100;
    }
    return 0;
}

int main()
{
    EnvInit(); //初始化环境变量
    while(1)
    {
        //1.打印命令行提示符
        PrintCmdPrompt();

        //2.获取命令行参数
        char commandline[COMMAND_SIZE];
        if(!GetArguments(commandline, sizeof(commandline))) //获取失败
            continue;
        
        //3.解析命令行参数
        if(!CommandParse(commandline))
            continue;
        //PrintArg();
        
        //4.检测是否为内建命令,是内建命令就执行
        if(CheckAndExeBuiltinCmd())
            continue; 

        //5.执行命令
        Execute();
    }
    return 0;
}
